/*
 * @Author: hongbin
 * @Date: 2022-11-02 18:05:46
 * @LastEditors: hongbin
 * @LastEditTime: 2022-12-03 20:58:41
 * @Description:只负责socket的node程序
 */
const http = require("http");
const express = require("express");
const app = express();
const { Server } = require("socket.io");

const port = process.env.PORT || 3001;

const httpServer = http.createServer(app);
httpServer.listen(port, () => { console.log("> socket服务器开启 端口：" + port) });

app.all("*", function (req, res, next) {
    //设置允许跨域的域名，*代表允许任意域名跨域
    res.header("Access-Control-Allow-Origin", "*");
    //允许的header类型
    // res.header("Access-Control-Allow-Headers", ["content-type", "Authorization"]);
    res.header("Access-Control-Allow-Headers", "content-type,Authorization");
    //跨域允许的请求方式
    res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
    if (req.method.toLowerCase() == "options") res.status(200).send();
    //让options尝试请求快速结束
    else next();
});

app.post("*", (req, res, next) => {
    let payload = "";
    req.on("data", (chunk) => (payload += chunk));
    req.on("end", () => {
        req.payload = JSON.parse(payload);
        next();
    });
});

const io = new Server(httpServer, { cors: true });

class MyMap {
    data = {};
    size = 0;
    set(key, value) {
        this.data[key] = value;
        this.size++;
    }
    clear() {
        this.data = {};
        this.size = 0;
    }
}

/**
* 实时多人运动技术实施方案
收集信息 所有有效socket(有人物信息的)
发送信息 发送当前所有人物信息(朝向，位置，配件)
闲暇时 停止计算
有有效连接再轮训
*/

class SyncMove {

    /**
    * 收集用户上交的信息 攒够了一起发送
    */
    collect = new MyMap();
    //有效的socket实例
    payloadSocket = [];
    //计时器
    timeout = 0;
    //正在主震计算中
    running = false;
    //每次统计信息间隔(毫秒)
    spaceTine = 600;

    constructor() {

    }

    //逐帧
    frameByFrame() {
        this.collectInfo();
        this.timeout = setTimeout(() => {
            this.frameByFrame();
        }, this.spaceTine)
    }

    stopFrameByFrame() {
        clearTimeout(this.timeout);
        this.running = false;
        const allSockets = io.sockets.sockets;
        allSockets.forEach(s => {
            if (s.__userId) {
                s.emit('onLineUserNotEnough');
            }
        })
    }

    /**
    * 计算当前是否有必要进行实时监听
    */
    isCanRun() {
        const allSockets = io.sockets.sockets;

        if (allSockets.size < 2) {
            console.log('人数不足计算');
            this.stopFrameByFrame();
            return;
        }
        let payloadCount = 0;
        allSockets.forEach(s => {
            if (s.__userId) {
                payloadCount++;
            }
        })
        if (payloadCount < 2) {
            this.stopFrameByFrame();
            return console.log('有效人数不足计算');
        }
        //之前没运行 现在满足条件了
        if (!this.running) {
            this.frameByFrame();
            this.running = true;
        }
    }

    /**
    * 要求客户端上传信息 接收信息统一处理
    */
    collectInfo() {
        const allSockets = io.sockets.sockets;
        this.payloadSocket = [];
        allSockets.forEach(s => {
            if (s.__userId) {
                this.payloadSocket.push(s)
            }
        })

        this.payloadSocket.forEach(socket => {
            //向客户端索取信息
            socket.emit('uploadActor')
            this.collect.clear();
        })
    }

    //接收每个客户端上交的信息 全部完毕后统一发送
    addCollect(info) {
        this.collect.set(info._id, info);
        //每个客户端都上传了信息
        if (this.collect.size == this.payloadSocket.length) {
            // console.log('统一发放：', this.collect);
            this.payloadSocket.forEach(socket => {
                //向客户端发送全部用户的信息
                socket.emit('actorUpdate', this.collect.data, this.spaceTine)
                // console.log(this.collect);
            })
            this.collect.clear();
        }
    }

}

const syncMove = new SyncMove()

// app.get('/socket', (req, res) => {

//     syncMove.collectInfo()

//     res.end()
// })


io.on('connection', (socket) => {
    console.log('socket连接成功', socket.id, Date.now());
    // socket.onAny((event, data) => {
    //     console.log(`监听到客户端发送${event}事件，携带数据:`, data);
    // })
    function on(...args) {
        socket.on(...args);
    }
    function emit(...args) {
        socket.emit(...args);
    }
    function broadcast(...args) {
        socket.broadcast.emit(...args);
    }

    /**
    * 根据用户的id找到他的socket实例
    */
    const toSocket = (id) => {
        let s;
        for (const [_, socket] of io.sockets.sockets) {
            if (socket.__userId == id) {
                s = socket;
                break;
            }
        }
        if (!s) {
            console.log('通过id查找socket 失败', id);
            s = { emit: (...rest) => { } }
        }
        return s
    }

    /**
    * 客户端 配件用户信息加载完毕 可以监听其他人员位置移动了
    */
    on('IAmReady', () => {
        console.log('i am ready ' + socket.__userId);
        syncMove.isCanRun()
    })

    /**
    * 更改客户端id同scoket实例id方便查找
    */
    on('setSocketId', (id) => {
        console.log('重制socketId:', id);
        socket.__userId = id;
        syncMove.isCanRun()
    })
    //退出登陆
    on('signOut', id => {
        delete socket.__userId;
        syncMove.isCanRun()
    })
    /**
    * 有用户断开连接 通知所有人在线人数变更
    */
    on('disconnect', () => {
        console.log('断开连接 disconnect');
        broadcast('onlinePeopleNumberChange', io.engine.clientsCount)
        socket.__userId && broadcast('leaveUserId', socket.__userId)
        socket.disconnect(true);
        io.sockets.sockets.delete(socket.id);
        //检测有用户离开后是否还有必要同步移动
        syncMove.isCanRun();
    })
    /**
    * 有用户断开连接 通知所有人在线人数变更
    */
    broadcast('onlinePeopleNumberChange', io.engine.clientsCount)


    //第一次连接不会接收到广播通知 手动请求在线人数的事件
    on('getOnlineCount', () => {
        //返回当前在线人数
        emit('getOnlineCount', io.engine.clientsCount)
    })
    //新的世界消息发送 广播所有人
    on('sendWorldNews', (info) => {
        // console.log("监听到世界消息：", info);
        broadcast('newWorldNews', info)
    })

    //请求加好友
    on('requestAddFriend', ({ applicant, applicantName, respondent }) => {
        socket.__userId = applicant;
        console.log("请求加好友：", { applicant, applicantName, respondent });

        toSocket(respondent).emit('haveRequestAddFriend', { applicant, applicantName });
        // io.socket.sockets[respondent].emit('requestAddFriend', { applicant, applicantName })
    })
    //同意加好友
    on('agreeAddFriend', ({ self, selfName, other, chatRecordId }) => {
        console.log(`${selfName}同意加 ${other} 为好友`);
        toSocket(other).emit('agreeAddFriend', { other: self, otherName: selfName, chatRecordId })
    })

    //发送消息给好友
    on('sendMessageToFriend', ({ content, type, sender, date, recordId, to }) => {
        socket.__userId = sender;
        console.log(`${sender} 发送 ${content} 到 ${recordId}`);
        toSocket(to).emit('friendSendMessageToMe', { content, type, sender, date, recordId })
    })

    /**
    * 将用户信息保存 
    */
    on('uploadActor', info => {
        syncMove.addCollect(info);
    })

    /**
    * 给其他人发送临时消息
    */
    on('temporaryMessage', data => {
        // to content from
        toSocket(data.to).emit('temporaryMessage', {
            content: data.content,
            sender: data.from,
            senderName: data.senderName,
        })
    })
});

//异常关闭
io.engine.on("connection_error", (err) => {
    console.log('异常关闭!');
    console.log(err.req);      // the request object
    console.log(err.code);     // the error code, for example 1
    console.log(err.message);  // the error message, for example "Session ID unknown"
    console.log(err.context);  // some additional error context
});